React Compiler 的運作流程很複雜,概括來說會執行以下幾個步驟:
抽象語法樹 (AST) 是一種樹狀結構,用來表示程式碼的語法結構。如果好奇怎麼呈現的可以使用 AST Explorer 來觀察。
首先先介紹 控制流圖 (Control-Flow Graph,CFG),控制流圖是一種圖形表示法,用來表示程式在執行過程中可能走訪的所有路徑。
控制流圖能幫助編譯器理解程式的執行路徑,而將抽象語法樹(AST)轉換成 HIR 的過程稱為 Lowering,而 HIR 是一種呈現控制流圖的方式,呈現方式會是用一個個區塊 (block) 組成。
SSA 是一種表示方式,確保每個變數在程式中只被賦值一次,可以讓編譯器能更容易進行優化。
簡易範例說明:
let a = 5;
let b = a + 2;
在 SSA 形式下,可能會被表示成這樣:
a1 = 5
b1 = a1 + 2
a1 和 b1 會是唯一的變數名稱,
常數傳播是一種優化技術,將變數替換為已知的常數值來提高程式的執行效率。
這是一個簡單的範例:
function calculateTotal(price: number) {
const taxRate = 0.1; // 這是一個常數
const total = price + price * taxRate;
return total;
}
會轉換成
function calculateTotal(price: number) {
const total = price + price * 0.1; // 直接使用常數
return total;
}
假設有一個簡單 Component
export default function MyApp() {
const [count, setCount] = useState(0);
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Add</button>
</div>
);
}
透過 React Compiler 最後會被轉換成這樣
function MyApp() {
const $ = _c(2);
const [count, setCount] = useState(0);
let t0;
// 檢查快取的第一個項目是否與 count 相同
if ($[0] !== count) {
t0 = (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>Add</button>
</div>
);
// 更新快取的第一個項目為當前的 count
$[0] = count;
// 更新快取的第二個項目為當前渲染的內容
$[1] = t0;
} else {
// 如果相同,則直接使用快取的渲染結果
t0 = $[1];
}
return t0;
}
在裡面有一個函數是 _c
,實際上使用的是 useMemoCache
的函數。useMemoCache
的函數是 React Compiler 內部使用的,用來管理 memoization 所用的快取。
更詳細的細節可以在 React Playground 進行測試,查看每一個流程的結果。
參考資料:
https://github.com/facebook/react/blob/main/compiler/docs/DESIGN_GOALS.md#architecture
https://yongseok.me/blog/en/react_compiler_1/
https://yongseok.me/blog/en/react_compiler_2/
https://yongseok.me/blog/en/react_compiler_3/
https://yongseok.me/blog/en/react_compiler_4/
https://www.youtube.com/watch?v=PYHBHK37xlE
https://www.youtube.com/watch?v=uA_PVyZP7AI
https://en.wikipedia.org/wiki/Control-flow_graph
https://zh.wikipedia.org/zh-tw/%E9%9D%99%E6%80%81%E5%8D%95%E8%B5%8B%E5%80%BC%E5%BD%A2%E5%BC%8F